﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SurfaceCardsBoardLibrary.Interfaces;
using System.Windows;
using SurfaceCardsGameKit.Tools;
using Microsoft.Surface.Presentation.Controls;
using System.Windows.Media.Animation;
using System.Windows.Media;
using SurfaceCardsBoardLibrary;

namespace SurfaceCardsGameKit.Dispositions
{
    class FanDisposition : IDisposition
    {
        private List<DispositionOffset> _offsets;

        #region IDisposition Members

        public void Render(IDeck deck, Size cardSize, Rect destinationRect, Point center,
            double deckOrientation, bool useTransitions, bool sizeChanged, DeckOrientation deckupDownOrientation, double durationRatio)
        {

            if (!sizeChanged && _offsets != null)
            {
                int i = 0;
                foreach (var card in deck)
                {
                    if (card.ScatterViewItem.Visibility == Visibility.Collapsed)
                        continue;

                    ActuallyMoveChilds(useTransitions, deck, card, _offsets[i], durationRatio);
                    i++;
                }
            }
            else
            {
                // put in line
                if (cardSize.Width * deck.Count <= destinationRect.Width)
                {
                    MoveChilds(deck, cardSize, destinationRect, 1, double.NaN, true, deckupDownOrientation, durationRatio);
                    return;
                }

                var ratio = cardSize.Width / (destinationRect.Width * deck.Count);

                double angleDegree = ratio * 50;
                double radius = Math.Pow(destinationRect.Width, 3) * deck.Count / Math.Pow(cardSize.Width, 3);

                MoveChilds(deck, cardSize, destinationRect, angleDegree, radius, useTransitions, deckupDownOrientation, durationRatio);
            }

        }

        void MoveChilds(IDeck deck, Size cardSize, Rect destinationRect, double angleDegree, double circleRadius, bool useTransitions,
            DeckOrientation deckupDownOrientation, double durationRatio)
        {
            _offsets = new List<DispositionOffset>();

            bool isEven = deck.Count % 2 == 0;
            double i;

            if (isEven)
                i = (deck.Count - 1) / -2.0;
            else
                i = -Math.Floor(deck.Count / 2.0);

            DispositionOffset[] offsets = new DispositionOffset[deck.Count];

            // first pass : calculate positions
            var j = 0;
            foreach (var card in deck)
            {
                var svi = card.ScatterViewItem;

                Point offsetPoint = new Point(0, 0);
                if (card.IsSelected)
                {
                    switch (deckupDownOrientation)
                    {
                        case DeckOrientation.Up:
                            offsetPoint.Y = -20;
                            break;
                        case DeckOrientation.Down:
                            offsetPoint.Y = 20;
                            break;
                    }
                }

                offsets[j] = CalcOffset(angleDegree * i, circleRadius, cardSize, destinationRect, offsetPoint, deckupDownOrientation);
                i++;
                j++;
            }

            double offsetX = offsets.Max(o => o.Max.X) - offsets.Min(o => o.Min.X);
            //offsetX = (offsets[offsets.Length - 1].Center.X + cardSize.Width) - (offsets[0].Center.X - cardSize.Width / 2);
            double offsetY = offsets.Max(o => o.Max.Y) - offsets.Min(o => o.Min.Y);

            // ratio can be equal to 0 when cards are in line
            double ratioX = offsetX == 0 ? 1 : (destinationRect.Width / offsetX);
            double ratioY = offsetY == 0 ? 1 : (destinationRect.Height / offsetY);

            var middle = new Point(destinationRect.X + destinationRect.Width / 2, destinationRect.Y + destinationRect.Height / 2);

            j = 0;
            foreach (var card in deck)
            {
                if (card.ScatterViewItem.Visibility == Visibility.Collapsed)
                    continue;

                var svi = card.ScatterViewItem;
                var offset = offsets[j];

                offset.Center = new Point(
                            middle.X + offset.Center.X * ratioX,
                            middle.Y + offset.Center.Y * ratioY
                        );

                _offsets.Add(offset);

                ActuallyMoveChilds(useTransitions, deck, card, offset, durationRatio);

                j++;
            }
        }

        private static void ActuallyMoveChilds(bool useTransitions, IDeck deck, ICard card, DispositionOffset offset, double durationRatio)
        {

            // translate Center to Deck position
            var center = deck.ScatterViewItem.TranslatePoint(offset.Center, deck.ScatterView);

            // calc real orientation
            var orientation = DispositionHelper.CalcOrientationForCards(
                card.ScatterViewItem.ActualOrientation, offset.Orientation + deck.ScatterViewItem.ActualOrientation);

            var svi = card.ScatterViewItem;

            bool centerChanged = svi.Center != offset.Center;
            bool orientationChanged = svi.Orientation != offset.Orientation;

            if (useTransitions)
            {
                var duration = DispositionHelper.ComputeAnimationDuration(svi.ActualCenter, center, svi.ActualOrientation, orientation, durationRatio);

                // center animation
                if (centerChanged)
                    DispositionHelper.BeginCenterAnimation(svi, center, duration);

                // orientation animation
                if (orientationChanged)
                    DispositionHelper.BeginOrientationAnimation(svi, orientation, duration);
            }
            else
            {
                if (centerChanged)
                    card.Center = center;
                if (orientationChanged)
                    card.Orientation = orientation;
            }
        }

        public bool CanApplyDisposition(Size cardSize, Rect destinationRect, int cardsCount)
        {
            return true;
            //return destinationRect.Width > cardsCount * (cardSize.Width / 4);
        }

        private DispositionOffset CalcOffset(double angleDegree, double circleRadius, Size cardSize, Rect destinationRect, Point offsetPoint, DeckOrientation deckupDownOrientation)
        {
            DispositionOffset off = new DispositionOffset();

            if (double.IsNaN(circleRadius))
            {
                // put in line
                off.Center =
                    new Point(
                        cardSize.Width * angleDegree,
                        0);
            }
            else
            {
                var middle = new Point(destinationRect.Width / 2.0, destinationRect.Height / 2.0);

                Matrix m = new Matrix()
                {
                    OffsetX = offsetPoint.X,
                    OffsetY = offsetPoint.Y,
                };

                var midCardWidth = cardSize.Width / 2.0;
                var midCardHeight = cardSize.Height / 2.0;

                Point[] points = new Point[4];

                Point p1, p2, p3, p4;

                TranslateRotateCopyMatrix(-midCardWidth, -midCardHeight, angleDegree, 0.0, circleRadius, out p1);

                // top right
                TranslateRotateCopyMatrix(midCardWidth, -midCardHeight, angleDegree, 0.0, circleRadius, out p2);

                // bottom right
                TranslateRotateCopyMatrix(midCardWidth, midCardHeight, angleDegree, 0.0, circleRadius, out p3);

                // bottom left
                TranslateRotateCopyMatrix(-midCardWidth, midCardHeight, angleDegree, 0.0, circleRadius, out p4);

                points[0] = p1;
                points[1] = p2;
                points[2] = p3;
                points[3] = p4;

                off.Min = new Point(points.Min(p => p.X), points.Min(p => p.Y));
                off.Max = new Point(points.Max(p => p.X), points.Max(p => p.Y));

                switch (deckupDownOrientation)
                {
                    case DeckOrientation.Up:
                        m.RotateAt(angleDegree, 0, circleRadius);
                        break;
                    case DeckOrientation.Down:
                        m.RotateAt(-angleDegree, 0, -circleRadius);
                        break;
                }

                off.Center = new Point(m.OffsetX, m.OffsetY);

                switch (deckupDownOrientation)
                {
                    case DeckOrientation.Up:
                        off.Orientation = angleDegree;
                        break;
                    case DeckOrientation.Down:
                        off.Orientation = -angleDegree;
                        break;
                }
            }

            return off;
        }

        private void TranslateRotateCopyMatrix(double x, double y, double angle, double rotationX, double rotationY, out Point p)
        {
            var m = new Matrix() { OffsetX = x, OffsetY = y };
            m.RotateAt(angle, rotationX, rotationY);


            p = new Point(m.OffsetX, m.OffsetY);
        }

        #endregion

        public void Cut(Card card)
        {
            var cardIndex = card.Deck.IndexOf(card);
            var cardsLeft = card.Deck.Where(c => card.Deck.IndexOf(c) < cardIndex).OfType<Card>();
            var cardsRight = card.Deck.Where(c => card.Deck.IndexOf(c) >= cardIndex).OfType<Card>();

            if (!cardsLeft.Any() || !cardsRight.Any())
                return;

            var leftoffset = CalcPositions(cardsLeft, true);
            var rightOffset = CalcPositions(cardsRight, false);

            // set the same animation duration for both
            var dispositionRect = card.Deck.GetCardsDestinationRect();
            var duration = DispositionHelper.ComputeAnimationDuration(dispositionRect.TopLeft, dispositionRect.TopRight, 0, 0, 1);

            leftoffset.Duration = rightOffset.Duration = duration;

            GatherCards(cardsLeft, true, leftoffset, rightOffset, null);
            GatherCards(cardsRight, false, rightOffset, leftoffset,
                (s, e) =>
                {
                    using (card.Deck.DeferUpdateDisposition(true, true, 2))
                    {
                        var cards = cardsLeft.ToList();
                        var deck = card.Deck;
                        foreach (var c in cards)
                        {
                            deck.Remove(c);
                            deck.Insert(deck.Count, c);
                        }
                    }
                }
            );

        }

        private DispositionOffset CalcPositions(IEnumerable<Card> allCards, bool left)
        {
            if (!allCards.Any())
                throw new Exception("Should have som cards");

            Card gatheredCard;
            if (left)
                gatheredCard = allCards.First();
            else
                gatheredCard = allCards.Last();

            Point dest = gatheredCard.ScatterViewItem.ActualCenter;

            DispositionOffset offset = new DispositionOffset
            {
                Center = dest,
                Orientation = gatheredCard.Deck.ScatterViewItem.ActualOrientation,
            };

            return offset;
        }

        private void GatherCards(IEnumerable<Card> allCards, bool isLeft, DispositionOffset beginOffset, DispositionOffset endOffset,
            EventHandler animsCompleted)
        {

            int i = 0;
            foreach (var card in allCards)
            {
                Point acutalDest = new Point(beginOffset.Center.X + i, beginOffset.Center.Y + i / 2);
                double actualOrientation = DispositionHelper.CalcOrientationForCards(card.ScatterViewItem.ActualOrientation, beginOffset.Orientation);

                var ca = DispositionHelper.BeginCenterAnimation(card.ScatterViewItem, acutalDest, beginOffset.Duration);
                DispositionHelper.BeginOrientationAnimation(card.ScatterViewItem, actualOrientation, beginOffset.Duration);

                if (card == allCards.Last())
                {
                    ca.Completed += (s, e) =>
                    {
                        // when first anim completed, begin second one
                        AnimateStacks(allCards, isLeft, beginOffset, endOffset, animsCompleted);
                    };
                }

                i++;
            }

        }

        private void AnimateStacks(IEnumerable<Card> cards, bool isLeft, DispositionOffset beginOffset, DispositionOffset endOffset,
            EventHandler animsCompleted)
        {

            int i = 0;
            foreach (var card in cards)
            {

                // stop current animations if there is any
                DispositionHelper.StopCenterAnimation(card.ScatterViewItem);
                DispositionHelper.StopOrientationAnimation(card.ScatterViewItem);

                Point actualbeginCenter = card.ScatterViewItem.ActualCenter;
                Point actualDestCenter = new Point(endOffset.Center.X + i, endOffset.Center.Y + i / 2);
                Point actualMiddleCenter;

                if (isLeft)
                    actualMiddleCenter = new Point(
                        (card.ScatterViewItem.ActualCenter.X + actualDestCenter.X) / 2,
                        card.ScatterViewItem.ActualCenter.Y + 200);
                else

                    actualMiddleCenter = new Point(
                        (card.ScatterViewItem.ActualCenter.X + actualDestCenter.X) / 2,
                        card.ScatterViewItem.ActualCenter.Y - 200);


                BezierSegment bs = new BezierSegment(
                    actualbeginCenter,
                    actualMiddleCenter,
                    actualDestCenter,
                    true);

                PathGeometry path = new PathGeometry();
                PathFigure figure = new PathFigure();
                figure.StartPoint = actualbeginCenter;
                figure.Segments.Add(bs);
                figure.IsClosed = false;
                path.Figures.Add(figure);

                PointAnimationUsingPath anim =
                    new PointAnimationUsingPath();
                anim.PathGeometry = path;
                anim.Duration = beginOffset.Duration + beginOffset.Duration;

                if (card == cards.Last())
                {
                    if (animsCompleted != null)
                        anim.Completed += animsCompleted;
                }

                card.ScatterViewItem.BeginAnimation(ScatterViewItem.CenterProperty, anim);

                i++;
            }
        }

    }
}
